import * as THREE from '../../build/three.module.js';
import Stats from '../../jsm/libs/stats.module.js';

let targetRotation = 0,
    targetRotationOnMouseDown = 0,
    mouseX = 0,
    mouseXOnMouseDown = 0,
    windowHalfX = window.innerWidth / 2;

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);

const camera = new THREE.PerspectiveCamera(65, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 150, 500);
scene.add(camera);

const light = new THREE.PointLight(0xffffff, .8);
camera.add(light);

const group = new THREE.Group();
group.position.y = 50;
scene.add(group);

const texture = new THREE.TextureLoader().load('../../textures/uv_grid_opengl.jpg');
// 为了在形状几何体中正确的显示贴图，下面的设置是必要的。
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(.008, .008);

// 添加线条模型
const addLineShape = (shape, color, x, y, z, rx, ry, rz, s) => {
  // 线条
  shape.aotoClose = true;

  const points = shape.getPoints();
  const spacedPoints = shape.getSpacedPoints(50);

  const gmtPts = new THREE.BufferGeometry().setFromPoints(points);
  const gmtSpacedPts = new THREE.BufferGeometry().setFromPoints(spacedPoints);

  // 实线
  let line = new THREE.Line(gmtPts, new THREE.LineBasicMaterial({ color: color }));
  line.position.set(x, y, z - 25);
  line.rotation.set(rx, ry, rz);
  line.scale.set(s, s, s);
  group.add(line);

  // 等距采样点连线
  line = new THREE.Line(gmtSpacedPts, new THREE.LineBasicMaterial({ color: color }));
  line.position.set(x, y, z + 25);
  line.rotation.set(rx, ry, rz);
  line.scale.set(s, s, s);
  group.add(line);

  // 顶点
  let particles = new THREE.Points(gmtPts, new THREE.PointsMaterial({ color: color, size: 4 }));
  particles.position.set(x, y, z + 75);
  particles.rotation.set(rx, ry, rz);
  particles.scale.set(s, s, s);
  group.add(particles);

  // 等距点
  particles = new THREE.Points(gmtSpacedPts, new THREE.PointsMaterial({ color: color, size: 4 }));
  particles.position.set(x, y, z + 125);
  particles.rotation.set(rx, ry, rz);
  particles.scale.set(s, s, s);
  group.add(particles);
}

// 添加形状模型
const addShape = (shape, exturdeSetting, color, x, y, z, rx, ry, rz, s) => {
  // 带贴图的扁平形状
  // THREE.ShapeBufferGeometry 创建的默认 uv 坐标只是简单的复用顶点的坐标
  let gmt = new THREE.ShapeBufferGeometry(shape);
  let mesh = new THREE.Mesh(gmt, new THREE.MeshPhongMaterial({ side: THREE.DoubleSide, map: texture }));
  mesh.position.set(x, y, z - 175);
  mesh.rotation.set(rx, ry, rz);
  mesh.scale.set(s, s, s);
  group.add(mesh);

  // 无贴图的扁平模型
  gmt = new THREE.ShapeBufferGeometry(shape);
  mesh = new THREE.Mesh(gmt, new THREE.MeshPhongMaterial({ color: color, side: THREE.DoubleSide }))
  mesh.position.set(x, y, z - 125);
  mesh.rotation.set(rx, ry, rz);
  mesh.scale.set(s, s, s);
  group.add(mesh);

  // 挤出形状
  gmt = new THREE.ExtrudeBufferGeometry(shape, exturdeSetting);
  mesh = new THREE.Mesh(gmt, new THREE.MeshPhongMaterial({ color: color }));
  mesh.position.set(x, y, z - 75);
  mesh.rotation.set(rx, ry, rz);
  mesh.scale.set(s, s, s);
  group.add(mesh);

  addLineShape(shape, color, x, y, z, rx, ry, rz, s);
}

// California 平面点组
const californiaPts = [];
californiaPts.push( new THREE.Vector2( 610, 320 ) );
californiaPts.push( new THREE.Vector2( 450, 300 ) );
californiaPts.push( new THREE.Vector2( 392, 392 ) );
californiaPts.push( new THREE.Vector2( 266, 438 ) );
californiaPts.push( new THREE.Vector2( 190, 570 ) );
californiaPts.push( new THREE.Vector2( 190, 600 ) );
californiaPts.push( new THREE.Vector2( 160, 620 ) );
californiaPts.push( new THREE.Vector2( 160, 650 ) );
californiaPts.push( new THREE.Vector2( 180, 640 ) );
californiaPts.push( new THREE.Vector2( 165, 680 ) );
californiaPts.push( new THREE.Vector2( 150, 670 ) );
californiaPts.push( new THREE.Vector2( 90, 737 ) );
californiaPts.push( new THREE.Vector2( 80, 795 ) );
californiaPts.push( new THREE.Vector2( 50, 835 ) );
californiaPts.push( new THREE.Vector2( 64, 870 ) );
californiaPts.push( new THREE.Vector2( 60, 945 ) );
californiaPts.push( new THREE.Vector2( 300, 945 ) );
californiaPts.push( new THREE.Vector2( 300, 743 ) );
californiaPts.push( new THREE.Vector2( 600, 473 ) );
californiaPts.push( new THREE.Vector2( 626, 425 ) );
californiaPts.push( new THREE.Vector2( 600, 370 ) );
californiaPts.push( new THREE.Vector2( 610, 320 ) );
for(let i = 0; i < californiaPts.length; i += 1) californiaPts[i].multiplyScalar(.25);
const californiaShape = new THREE.Shape(californiaPts);

// 三角形
const triangleShape = new THREE.Shape()
  .moveTo(80, 20)
  .lineTo(40, 80)
  .lineTo(120, 80)
  .lineTo(80, 20); // 手动闭合

// 心形
let x = 0, y = 0;
const heartShape = new THREE.Shape()
  .moveTo(x + 25, y + 25)
  .bezierCurveTo(x + 25, y + 25, x + 20, y, 0, 0)
  .bezierCurveTo(x - 30, y, x - 30, y + 35, x - 30, y + 35)
  .bezierCurveTo(x - 30, y + 55, x - 10, y + 77, x + 25, y + 95)
  .bezierCurveTo(x + 60, y + 77, x + 80, y + 55, x + 80, y + 35)
  .bezierCurveTo(x + 80, y + 35, x + 80, y, x + 50, y)
  .bezierCurveTo(x + 35, y, x + 25, y + 25, x + 25, y + 25);

// 正方形
const sqLength = 80;
const squareShape = new THREE.Shape()
  .moveTo(0, 0)
  .lineTo(0, sqLength)
  .lineTo(sqLength, sqLength)
  .lineTo(sqLength, 0)
  .lineTo(0, 0);

// 圆角矩形
const roundedRectShape = new THREE.Shape();
(function roundRect(ctx, x, y, width, height, radius) {
  ctx.moveTo(x, y + radius);
  ctx.lineTo(x, y + height - radius)
  ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
  ctx.lineTo(x + width - radius, y + height)
  ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
  ctx.lineTo(x + width, y + radius);
  ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
  ctx.lineTo(x + radius, y);
  ctx.quadraticCurveTo(x, y, x, y + radius);
})(roundedRectShape, 0, 0, 50, 50, 20);

// 轨道
const trackShape = new THREE.Shape()
  .moveTo(40, 40)
  .lineTo(40, 160)
  .absarc(60, 160, 20, Math.PI, 0, true)
  .lineTo(80, 40)
  .absarc(60, 40, 20, 2 * Math.PI, Math.PI, true);

// 圆
const circleRadius = 40;
const circleShape = new THREE.Shape()
  .moveTo(0, circleRadius)
  .quadraticCurveTo(circleRadius, circleRadius, circleRadius, 0)
  .quadraticCurveTo(circleRadius, -circleRadius, 0, -circleRadius)
  .quadraticCurveTo(-circleRadius, -circleRadius, -circleRadius, 0)
  .quadraticCurveTo(-circleRadius, circleRadius, 0, circleRadius);

// 鱼
x = y = 0;
const fishShape = new THREE.Shape()
  .moveTo(x, y)
  .quadraticCurveTo(x + 50, y - 80, x + 90, y - 10)
  .quadraticCurveTo(x + 100, y - 10, x + 115, y - 40)
  .quadraticCurveTo(x + 115, y, x + 115, y + 40)
  .quadraticCurveTo(x + 100, y + 10, x + 90, y + 10)
  .quadraticCurveTo(x + 50, y + 80, x, y)

// 中空圆环
const arcShape = new THREE.Shape()
  .moveTo(50, 10)
  .absarc(10, 10, 40, 0, Math.PI * 2, false);
const holePath = new THREE.Path()
  .moveTo(20, 10)
  .absarc(10, 10, 10, 0, Math.PI * 2, true);
arcShape.holes.push(holePath);

// 笑脸
const smileyShape = new THREE.Shape()
  .moveTo(80, 40)
  .absarc(40, 40, 40, 0, Math.PI * 2, false);
const smileyEye1Path = new THREE.Path()
  .moveTo(35, 20)
  .absellipse(25, 20, 10, 10, 0, Math.PI * 2, true);
const smileyEye2Path = new THREE.Path()
  .moveTo(65, 20)
  .absellipse(55, 20, 10, 10, 0, Math.PI * 2, true);
const smileyMouthPath = new THREE.Path()
  .moveTo(20, 40)
  .quadraticCurveTo(40, 60, 60, 40)
  .bezierCurveTo(70, 45, 70, 50, 60, 60)
  .quadraticCurveTo(40, 80, 20, 60)
  .quadraticCurveTo(5, 50, 20, 40);
smileyShape.holes.push(smileyEye1Path);
smileyShape.holes.push(smileyEye2Path);
smileyShape.holes.push(smileyMouthPath);

// 样条形状
const splinepts = [];
splinepts.push(new THREE.Vector2(70, 20));
splinepts.push(new THREE.Vector2(80, 90));
splinepts.push(new THREE.Vector2(-30, 70));
splinepts.push(new THREE.Vector2(0, 0));
const splineShape = new THREE.Shape()
  .moveTo(0, 0)
  .splineThru(splinepts);

// 挤出配置
const extrudeSettings = {
  depth: 8,
  bevelEnabled: true,
  bevelSegments: 2,
  steps: 2,
  bevelSize: 1,
  bevelThickness: 1
}
// 构建形状的几何体
addShape(californiaShape, extrudeSettings, 0xf08000, -300, -100, 0, 0, 0, 0, 1);
addShape(triangleShape, extrudeSettings, 0x8080f0, -180, 0, 0, 0, 0, 0, 1);
addShape(heartShape, extrudeSettings, 0xf00000, 60, 100, 0, 0, 0, Math.PI, 1);
addShape(squareShape, extrudeSettings, 0x0040f0, 150, 100, 0, 0, 0, 0, 1);
addShape(trackShape, extrudeSettings, 0x008080, 200, -100, 0, 0, 0, 0, 1);
addShape(circleShape, extrudeSettings, 0x00f000, 120, 250, 0, 0, 0, 0, 1);
addShape(fishShape, extrudeSettings, 0x404040, -60, 200, 0, 0, 0, 0, 1);
addShape(arcShape, extrudeSettings, 0x804000, 150, 0, 0, 0, 0, 0, 1);
addShape(smileyShape, extrudeSettings, 0xf000f0, -200, 250, 0, 0, 0, Math.PI, 1);
addShape(splineShape, extrudeSettings, 0x808080, -50, -100, 0, 0, 0, 0, 1);

addLineShape(arcShape.holes[0], 0x804000, 150, 0, 0, 0, 0, 0, 1);

for(let i = 0; i < smileyShape.holes.length; i += 1) {
  addLineShape(smileyShape.holes[i], 0xf000f0, -200, 250, 0, 0, 0, Math.PI, 1);
}

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const stats = new Stats();
document.body.appendChild(stats.dom);

const render = () => {
  group.rotation.y += (targetRotation - group.rotation.y) * .05;
  renderer.render(scene, camera);
}

const animate = () => {
  requestAnimationFrame(animate);

  stats.update();
  render();
}
animate();

const handleDocumentMouseMove = (e) => {
  mouseX = e.clientX - windowHalfX;
  targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * .02;
}

const handleDocumentMouseUp = () => {
  document.removeEventListener( 'mousemove', handleDocumentMouseMove, false );
  document.removeEventListener( 'mouseup', handleDocumentMouseUp, false );
  document.removeEventListener( 'mouseout', handleDocumentMouseOut, false );
}

const handleDocumentMouseOut = () => {
  document.removeEventListener( 'mousemove', handleDocumentMouseMove, false );
  document.removeEventListener( 'mouseup', handleDocumentMouseUp, false );
  document.removeEventListener( 'mouseout', handleDocumentMouseOut, false );
}

const handleDocumentMouseDown = (e) => {
  e.preventDefault();

  document.addEventListener('mousemove', handleDocumentMouseMove, false);
  document.addEventListener('mouseup', handleDocumentMouseUp, false);
  document.addEventListener('mouseout', handleDocumentMouseOut, false);

  mouseXOnMouseDown = e.clientX - windowHalfX;
  targetRotationOnMouseDown = targetRotation;
}
document.addEventListener('mousedown', handleDocumentMouseDown, false);

const handleWindowResize = () => {
  windowHalfX = window.innerWidth / 2;

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(innerWidth, innerHeight);
}
window.addEventListener('resize', handleWindowResize, false);